/*
 * Created on September 23, 2007
 *
 * Copyright (c) Patrick Armstrong 2002-2007. All Rights Reserved.
 */
package org.dime.mine.bot;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.dime.mine.game.Board;

/**
 * BoardZone // TODO Add type summary
 * 
 * @author Owner
 */
public class ZonedBoard extends AwareBoard
{
    private static final int NO_ZONE = -1;

    private int[][] zonedBoard;
    private final List<Zone> zones = new LinkedList<Zone>();

    /**
     * @param board
     */
    public ZonedBoard(Board board)
    {
        super(board);

        initializeZones();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.bot.AwareBoard#getAwareLocation(int, int)
     */
    @Override
    public AwareLocation getAwareLocation(int row, int column)
    {
        return getZonedLocation(row, column);
    }

    public ZonedLocation getZonedLocation(int row, int column)
    {
        return new ZonedLocation(row, column);
    }

    public List<Zone> getZones()
    {
        return zones;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.dime.mine.bot.AwareBoard#toString()
     */
    @Override
    public String toString()
    {
        StringBuffer str = new StringBuffer();
        int zoneIndex;
        for (int i = 0; i < getRows(); i++)
        {
            for (int j = 0; j < getColumns(); j++)
            {
                zoneIndex = getZonedLocation(i, j).getZone();
                switch (zoneIndex)
                {
                    case NO_ZONE:
                        str.append(".");
                        break;
                    default:
                        str.append(zoneIndex);
                }
            }
            str.append("\n");
        }

        for (Zone z : getZones())
        {
            if (z.isCyclic())
                str.append("There is a cycle in zone " + z.zoneIndex + "\n");
        }
        return str.toString();
    }

    private void initializeZones()
    {
        zonedBoard = new int[getRows()][getColumns()];
        for (int i = 0; i < getRows(); i++)
        {
            for (int j = 0; j < getColumns(); j++)
            {
                zonedBoard[i][j] = NO_ZONE;
            }
        }

        int zoneCount = 0;
        ZonedLocation zLoc;
        for (AwareLocation loc : getUnsolvedLocations())
        {
            zLoc = (ZonedLocation) loc;
            if (zLoc.getZone() == NO_ZONE)
            {
                zones.add(new Zone(zLoc, zoneCount++));
            }
        }
    }

    public class Zone
    {
        protected final int zoneIndex;
        private boolean cyclic = false;

        protected Zone(ZonedLocation seed, int zoneIndex)
        {
            this.zoneIndex = zoneIndex;
            initializeZone(seed);
        }

        public boolean isCyclic()
        {
            return cyclic;
        }

        public Set<ZonedLocation> getZoneLocations()
        {
            Set<ZonedLocation> set = new HashSet<ZonedLocation>();
            ZonedLocation zLoc;
            for (AwareLocation loc : getUnsolvedLocations())
            {
                zLoc = (ZonedLocation) loc;
                if (zLoc.getZone() == zoneIndex)
                    set.add(zLoc);
            }
            return set;
        }

        private void initializeZone(ZonedLocation seed)
        {
            if (!seed.isOpen())
                return;

            seed.setZone(zoneIndex);

            ZonedLocation zLoc;
            for (AwareLocation loc : seed.getNeighborhood(2))
            {
                zLoc = (ZonedLocation) loc;
                if (zLoc.isOpen())
                {
                    if (zLoc.getZone() == NO_ZONE
                            && hasSharedUnknown(seed, zLoc))
                        initializeZone(zLoc);
                    else if (zLoc.getZone() == zoneIndex
                            && seed.getRelatedNeighborhood().contains(zLoc))
                        this.cyclic = true;
                }
            }
        }

        private boolean hasSharedUnknown(AwareLocation locOne,
                AwareLocation locTwo)
        {
            Collection<AwareLocation> neighbors = locOne.getNeighborhood();
            for (AwareLocation n : locTwo.getNeighborhood())
            {
                if (n.isUnknown() && neighbors.contains(n))
                    return true;
            }
            return false;
        }
    }

    public class ZonedLocation extends AwareLocation
    {

        /**
         * @param row
         * @param column
         */
        public ZonedLocation(int row, int column)
        {
            super(row, column);
        }

        public List<ZonedLocation> getRelatedNeighborhood()
        {
            List<ZonedLocation> list = new ArrayList<ZonedLocation>(8);
            ZonedLocation zLoc;
            for (AwareLocation loc : super.getNeighborhood())
            {
                zLoc = (ZonedLocation) loc;
                if (zLoc.getZone() == getZone())
                    list.add(zLoc);
            }
            return list;
        }

        protected int getZone()
        {
            return zonedBoard[getRow()][getColumn()];
        }

        private void setZone(int zoneIndex)
        {
            zonedBoard[getRow()][getColumn()] = zoneIndex;
        }
    }
}
